package ch.trackedbean.binding.action;

import java.awt.event.*;
import java.util.*;
import java.util.concurrent.*;
import javax.swing.*;
import ch.trackedbean.common.*;

/**
 * Abstract implementation of an asynchronous action.<br>
 * This implementation provides the following hooks:<br> {@link #executionNeeded(ActionEvent, List)}<br> {@link #prepare(ActionEvent, List)}<br>
 * {@link #finish(ActionEvent, Object, Object, List)}<br>
 * The work will be executed in {@link #perform(ActionEvent, Object, List)}.<br>
 * If an exception gets thrown it will be passed to the {@link IExceptionHandler} returned by {@link #getExceptionHandler()}.
 * 
 * @author hautle
 * @param <S> The type of the selection
 * @param <P> The type of the prepared object (created by {@link #prepare(ActionEvent, List)})
 * @param <R> The type of the result object (created by {@link #perform(ActionEvent, Object, List)})
 */
public abstract class AbstractAsynchronousAction<S, P, R> extends AbstractSelectionAction<S> {
    /**
     * Default constructor.
     */
    public AbstractAsynchronousAction() {
    }

    /**
     * Default constructor.
     * 
     * @param name The name of the action
     * @param icon The icon of the action
     */
    public AbstractAsynchronousAction(String name, Icon icon) {
        super(name, icon);
    }

    /**
     * Default constructor.
     * 
     * @param name The name of the action
     */
    public AbstractAsynchronousAction(String name) {
        super(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void actionPerformed(ActionEvent e) {
        try {
            final List<S> selection = getSelection();
            if (!executionNeeded(e, selection))
                return;
            new Worker(e, prepare(e, selection), selection).execute();
        } catch (Exception e1) {
            getExceptionHandler().exceptionOccurred(e1);
        }
    }

    /**
     * Returns the exception handler to use.<br>
     * The default implementation just creates and returns an instance of {@link DefaultExceptionHandler}.
     * 
     * @return The exception handler
     */
    protected IExceptionHandler getExceptionHandler() {
        return DefaultExceptionHandler.getDefault();
    }

    /**
     * This method defines if the asynchronous part has to be executed.<br>
     * If you return false then the methods {@link #prepare(ActionEvent, List)}, {@link #perform(ActionEvent, Object, List)} and
     * {@link #finish(ActionEvent,Object, Object, List)} won't be called.
     * 
     * @param e The action event
     * @param selection The selection
     * @return True if the asynchronous part should be executed
     * @throws Exception If something went wrong (will be handled by the handler returned by {@link #getExceptionHandler()})
     */
    protected boolean executionNeeded(ActionEvent e, List<S> selection) throws Exception {
        return true;
    }

    /**
     * Hook method called before the asynchronous part starts.<br>
     * 
     * @param e The action event
     * @param selection The selection
     * @return The result of the preparation (will be passed to {@link #perform(ActionEvent,Object, List)})
     * @throws Exception If something went wrong (will be handled by the handler returned by {@link #getExceptionHandler()})
     */
    protected P prepare(ActionEvent e, List<S> selection) throws Exception {
        return null;
    }

    /**
     * Asynchronous part of the action.<br>
     * 
     * @param e The action event
     * @param prepared The result of {@link #prepare(ActionEvent, List)}
     * @param selection The selection
     * @return A result (will be passed to {@link #finish(ActionEvent, Object, Object, List)})
     * @throws Exception If something went wrong (will be handled by the handler returned by {@link #getExceptionHandler()})
     */
    public abstract R perform(ActionEvent e, P prepared, List<S> selection) throws Exception;

    /**
     * Hook method called after the execution of {@link #perform(ActionEvent, Object, List)} (also if the call failed).<br>
     * Use this method for releasing locks etc.<br>
     * This method gets executed on the Event Dispatch Thread.
     * 
     * @param e The action event
     * @param prepare The result returned by {@link #prepare(ActionEvent, List)}
     * @param result The result returned by {@link #perform(ActionEvent, Object, List)} or null if the call failed
     * @param selection The selection
     */
    protected void finish(ActionEvent e, P prepare, R result, List<S> selection) {
    }

    /**
     * The worker executing the asynchronous part.
     * 
     * @author hautle
     */
    protected class Worker extends SwingWorker<R, Void> {
        /** The action event. */
        private final ActionEvent event;

        /** The selection. */
        private final List<S> selection;

        /** The result of {@link AbstractAsynchronousAction#prepare(ActionEvent, List)}. */
        private P prepared;

        /**
         * Default constructor.
         * 
         * @param event The action event
         * @param prepared The result of {@link AbstractAsynchronousAction#prepare(ActionEvent, List)}.
         * @param selection The selection
         */
        protected Worker(ActionEvent event, P prepared, List<S> selection) {
            this.event = event;
            this.selection = selection;
            this.prepared = prepared;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected R doInBackground() throws Exception {
            return perform(event, prepared, selection);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void done() {
            R res = null;
            try {
                res = get();
            } catch (InterruptedException e) {
                getExceptionHandler().exceptionOccurred(e);
            } catch (ExecutionException e) {
                getExceptionHandler().exceptionOccurred(e.getCause());
            } finally {
                finish(event, prepared, res, selection);
            }
        }
    }
}
